﻿using DevExpress.ExpressApp.ConditionalAppearance;
using DevExpress.ExpressApp.DC;
using DevExpress.Persistent.Validation;
using DevExpress.Xpo;
using DevExpress.Xpo.DB;
using EasyXaf.EntityDesigner.BusinessObjects;
using EasyXaf.EntityDesigner.BusinessObjects.Entities;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.Appearances;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.Associations;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.EntityProperties;
using EasyXaf.EntityDesigner.BusinessObjects.Entities.ValidationRules;
using EasyXaf.EntityDesigner.BusinessObjects.Enums;
using EasyXaf.EntityDesigner.CodeTemplates.Extensions;
using EasyXaf.EntityDesigner.Tools.Extensions;
using System.Reflection;

namespace EasyXaf.EntityDesigner.Tools;

public class XpoDomainModelGenerator
{
    private static DomainModel CreateDomainModel(Assembly assembly, Session session)
    {
        return new DomainModel(session)
        {
            Name = assembly.GetName().Name,
            Version = assembly.GetName().Version.ToString(),
            Description = assembly.FullName
        };
    }

    private static Enumeration CreateEnumeration(ITypeInfo typeInfo, DomainModel domainModel)
    {
        var enumeration = domainModel.Enums.FirstOrDefault(e => e.Name == typeInfo.Name);
        if (enumeration == null)
        {
            var caption = typeInfo.GetDescription().ToLines().FirstOrDefault();
            var description = typeInfo.GetDescription();

            if (caption == description)
            {
                description = null;
            }

            enumeration = new Enumeration(domainModel.Session)
            {
                Name = typeInfo.GetName(),
                Namespace = typeInfo.GetNamespace(),
                Caption = caption,
                Description = description,
                DomainModel = domainModel,
            };

            var order = 0;
            var enumType = typeInfo.Type;
            var enumValues = Enum.GetValues(enumType);
            foreach (var enumValue in enumValues)
            {
                var fieldInfo = typeInfo.Type.GetField(enumValue.ToString());
                var value = (int)Convert.ChangeType(enumValue, Enum.GetUnderlyingType(enumType), null);
                CreateEnumValue(fieldInfo, fieldInfo.Name, value, order++, enumeration);
            }
        }
        return enumeration;
    }

    private static EnumValue CreateEnumValue(MemberInfo memberInfo, string name, int? value, int order, Enumeration enumeration)
    {
        return new EnumValue(enumeration.Session)
        {
            Name = name,
            Caption = memberInfo.GetCaption(),
            Description = memberInfo.GetDescription(),
            ImageName = memberInfo.GetImageName(),
            Value = value.HasValue && value.Value != order ? value : null,
            Order = order,
            Enum = enumeration,
        };
    }

    private static Entity CreateEntity(ITypeInfo typeInfo, DomainModel domainModel)
    {
        var entity = new Entity(domainModel.Session)
        {
            Name = typeInfo.GetName(),
            Namespace = typeInfo.GetNamespace(),
            Caption = typeInfo.GetCaption(),
            CaptionFormat = typeInfo.GetCaptionFormat(),
            Category = typeInfo.GetCategory(),
            Description = typeInfo.GetDescription(),
            ImageName = typeInfo.GetImageName(),
            TableName = typeInfo.GetTableName(),
            IsAbstract = typeInfo.GetIsAbstract(),
            IsCloneable = typeInfo.GetIsCloneable().ToDefaultBoolean(),
            IsCreatable = typeInfo.GetIsCreatable().ToDefaultBoolean(),
            IsVisibleInNavigations = typeInfo.GetIsVisibleInNavigations().ToDefaultBoolean(),
            IsVisibleInReports = typeInfo.GetIsVisibleInReports().ToDefaultBoolean(),
            IsVisibleInDashboards = typeInfo.GetIsVisibleInDashboards().ToDefaultBoolean(),
            IsDeferredDeletion = typeInfo.GetIsDeferredDeletion().ToDefaultBoolean(),
            IsOptimisticLocking = typeInfo.GetIsOptimisticLocking().ToDefaultBoolean(),
            DomainModel = domainModel,
        };

        entity.SetLocation([0, 0, 0, 0], domainModel);

        return entity;
    }

    private static EntityProperty CreateEntityProperty(IMemberInfo memberInfo, int order, Entity entity)
    {
        return new EntityProperty(entity.Session)
        {
            Name = memberInfo.GetName(),
            Caption = memberInfo.GetCaption(),
            Description = memberInfo.GetDescription(),
            ColumnName = memberInfo.GetColumnName(),
            IsEditable = memberInfo.GetIsEditable().ToDefaultBoolean(),
            IsCloneable = memberInfo.GetIsCloneable().ToDefaultBoolean(),
            IsBrowsable = memberInfo.GetIsBrowsable().ToDefaultBoolean(),
            IsVisibleInDetailView = memberInfo.GetIsVisibleInDetailView().ToDefaultBoolean(),
            IsVisibleInListView = memberInfo.GetIsVisibleInListView().ToDefaultBoolean(),
            IsVisibleInLookupListView = memberInfo.GetIsVisibleInLookupListView().ToDefaultBoolean(),
            IsVisibleInReports = memberInfo.GetIsVisibleInReports().ToDefaultBoolean(),
            IsImmediatePost = memberInfo.GetIsImmediatePost().ToDefaultBoolean(),
            NoForeignKeyConstraints = memberInfo.GetNoForeignKeyConstraints().ToDefaultBoolean(),
            IsPrimaryKey = memberInfo.GetIsPrimaryKey(),
            IsPersistent = memberInfo.GetIsPersistent().ToDefaultBoolean(),
            IsFetchOnly = memberInfo.GetIsFetchOnly().ToDefaultBoolean(),
            IsReadonly = memberInfo.GetIsReadOnly(),
            IsNullable = memberInfo.GetIsNullable().ToDefaultBoolean(),
            PropertyEditor = memberInfo.GetPropertyEditor(),
            PropertyType = memberInfo.GetPropertyType(),
            ColumnDefaultValue = memberInfo.GetColumnDefaultValue(),
            Order = order,
            Entity = entity,
        };
    }

    private static void CreateEntityPropertyType(IMemberInfo memberInfo, EntityProperty entityProperty, Dictionary<Entity, ITypeInfo> entities, Dictionary<DomainModel, Assembly> domainModels)
    {
        var memberTypeInfo = memberInfo.MemberTypeInfo.UnderlyingTypeInfo;

        if (entityProperty.PropertyType == PropertyType.String)
        {
            var size = memberInfo.GetSize();
            if (size == -1)
            {
                entityProperty.PropertyType = PropertyType.LongString;
            }
            else if (size.HasValue)
            {
                entityProperty.StringLength = size.Value;
            }
        }
        else if (entityProperty.PropertyType == PropertyType.Enumeration)
        {
            var domainModel = domainModels.Keys.FirstOrDefault(m => domainModels[m] == memberTypeInfo.Type.Assembly);
            if (domainModel != null)
            {
                entityProperty.EnumType = CreateEnumeration(memberTypeInfo, domainModel);
            }
            else
            {
                entityProperty.EnumTypeFullName = memberTypeInfo.Type.FullName;
            }
        }
        else if (entityProperty.PropertyType == PropertyType.Entity)
        {
            entityProperty.EntityType = entities.Keys.FirstOrDefault(e => entities[e].Type == memberTypeInfo.Type);
        }
        else if (entityProperty.PropertyType == PropertyType.EntityCollection)
        {
            entityProperty.EntityType = entities.Keys.FirstOrDefault(e => entities[e].Type == memberInfo.ListElementTypeInfo.Type);
        }

        if (entityProperty.PropertyType == PropertyType.Entity || entityProperty.PropertyType == PropertyType.EntityCollection)
        {
            var dataSource = memberInfo.GetDataSourceCriteria();
            if (!string.IsNullOrWhiteSpace(dataSource))
            {
                entityProperty.DataSourceType = DataSourceType.DataSourceCriteria;
                entityProperty.DataSource = dataSource;
            }
            else
            {
                dataSource = memberInfo.GetDataSourceCriteriaProperty();
                if (!string.IsNullOrWhiteSpace(dataSource))
                {
                    entityProperty.DataSourceType = DataSourceType.DataSourceCriteriaProperty;
                    entityProperty.DataSource = dataSource;
                }
                else
                {
                    dataSource = memberInfo.GetDataSourceProperty();
                    if (!string.IsNullOrWhiteSpace(dataSource))
                    {
                        entityProperty.DataSourceType = DataSourceType.DataSourceProperty;
                        entityProperty.DataSource = dataSource;
                    }
                }
            }
        }
    }

    private static RequiredRule CreateRequiredRule(IMemberInfo memberInfo, Entity entity)
    {
        var rule = memberInfo.FindAttribute<RuleRequiredFieldAttribute>(false);
        if (rule != null)
        {
            return new RequiredRule(entity.Session)
            {
                TargetProperty = entity.Properties.FirstOrDefault(p => p.Name == memberInfo.Name),
                ErrorMessage = rule.CustomMessageTemplate,
                Entity = entity,
            };
        }
        return null;
    }

    private static UniqueValueRule CreateUniqueValueRule(IMemberInfo memberInfo, Entity entity)
    {
        var rule = memberInfo.FindAttribute<RuleUniqueValueAttribute>(false);
        if (rule != null)
        {
            return new UniqueValueRule(entity.Session)
            {
                TargetProperty = entity.Properties.FirstOrDefault(p => p.Name == memberInfo.Name),
                ErrorMessage = rule.CustomMessageTemplate,
                Entity = entity,
            };
        }
        return null;
    }

    private static RangeRule CreateRangeRule(IMemberInfo memberInfo, Entity entity)
    {
        var rule = memberInfo.FindAttribute<RuleRangeAttribute>(false);
        if (rule != null)
        {
            var rangeRule = (IRuleRangeProperties)rule;
            return new RangeRule(entity.Session)
            {
                MinValue = rangeRule.MinimumValue + string.Empty,
                MaxValue = rangeRule.MaximumValue + string.Empty,
                TargetProperty = entity.Properties.FirstOrDefault(p => p.Name == memberInfo.Name),
                ErrorMessage = rule.CustomMessageTemplate,
                Entity = entity,
            };
        }
        return null;
    }

    private static FromBoolPropertyRule CreateFromBoolPropertyRule(IMemberInfo memberInfo, Entity entity)
    {
        var rule = memberInfo.FindAttribute<RuleFromBoolPropertyAttribute>(false);
        if (rule != null)
        {
            if (memberInfo.Name == "IsValid" && !string.IsNullOrWhiteSpace(rule.CustomMessageTemplate))
            {
                entity.ValidationMessage = rule.CustomMessageTemplate;
            }

            return new FromBoolPropertyRule(entity.Session)
            {
                TargetProperty = entity.Properties.FirstOrDefault(p => p.Name == memberInfo.Name),
                ErrorMessage = rule.CustomMessageTemplate,
                UsedProperties = rule.UsedProperties,
                Entity = entity,
            };
        }
        return null;
    }

    private static Association CreateAssociation(IMemberInfo memberInfo, Entity entity, Dictionary<Entity, ITypeInfo> entities)
    {
        var attribute = memberInfo.FindAttribute<AssociationAttribute>();
        if (attribute != null)
        {
            var associatedMember = memberInfo.AssociatedMemberInfo;

            var associationName = attribute.Name;
            if (string.IsNullOrWhiteSpace(associationName))
            {
                associationName = $"{associatedMember.Name}-{memberInfo.Name}";
            }

            // 一对多，当前实体为父实体，只需要子实体创建关系，否则无需创建
            if (!associatedMember.IsList && memberInfo.IsList)
            {
                return null;
            }

            var associatedEntity = entities.Keys.FirstOrDefault(e => entities[e] == associatedMember.Owner);

            // 一对一，多对多，如果关联实体已创建了关系，否则无需创建
            if ((!associatedMember.IsList && !memberInfo.IsList) || (associatedMember.IsList && memberInfo.IsList))
            {
                if (associatedEntity.Associations.Any(a => a.Name == associationName))
                {
                    return null;
                }
            }

            var entityProperty = entity.AllProperties.FirstOrDefault(p => p.Name == memberInfo.Name);
            var associatedEntityProperty = associatedEntity.AllProperties.FirstOrDefault(p => p.Name == associatedMember.Name);

            var associationType = AssociationType.OneToMany;

            if (associatedMember.IsList && memberInfo.IsList)
            {
                associationType = AssociationType.ManyToMany;
            }
            else if (!associatedMember.IsList && !memberInfo.IsList)
            {
                associationType = AssociationType.OneToOne;
            }

            var isAggregated = memberInfo.GetIsAggregated();
            if (!isAggregated && memberInfo.AssociatedMemberInfo != null)
            {
                isAggregated = memberInfo.AssociatedMemberInfo.GetIsAggregated();
            }

            return new Association(entity.Session)
            {
                Name = associationName,
                Parent = associatedEntity,
                ParentProperty = associatedEntityProperty,
                Child = entity,
                ChildProperty = entityProperty,
                AssociationType = associationType,
                IsAggregated = isAggregated,
                Entity = entity,
            };
        }
        return null;
    }

    private static List<Appearance> CreateAppearances(IBaseInfo baseInfo, Entity entity, EntityProperty entityProperty = null)
    {
        var attributes = baseInfo.FindAttributes<AppearanceAttribute>();
        return attributes.Select(attribute => new Appearance(entity.Session)
        {
            AppearanceId = attribute.Id,
            AppearanceItemType = attribute.AppearanceItemType == "ViewItem" ? null : attribute.AppearanceItemType,
            TargetItems = attribute.TargetItems,
            Criteria = attribute.Criteria,
            Context = attribute.Context == "Any" ? null : attribute.Context,
            Method = attribute.Method,
            BackColor = attribute.BackColor,
            FontColor = attribute.FontColor,
            FontStyle = ((IAppearance)attribute).FontStyle,
            Enabled = ((IAppearance)attribute).Enabled,
            Visibility = ((IAppearance)attribute).Visibility,
            Priority = attribute.Priority == 0 ? null : attribute.Priority,
            TargetProperty = entityProperty,
            Entity = entity,
        }).ToList();
    }

    public static void Generate(ITypesInfo typesInfo, string dbFile)
    {
        Generate(typesInfo.PersistentTypes, dbFile);
    }

    public static void Generate(IEnumerable<ITypeInfo> typeInfos, string dbFile)
    {
        var dataStore = SQLiteConnectionProvider.CreateProviderFromString($"Data Source=\"{dbFile}\";Pooling=False", AutoCreateOption.DatabaseAndSchema, out var objectsToDisposeOnDisconnect);
        using var dataLayer = new SimpleDataLayer(dataStore);
        dataLayer.Dictionary.CollectClassInfos(typeof(XpoDomainModelGenerator).Assembly);
        using var session = new UnitOfWork(dataLayer);

        var domainModels = new Dictionary<DomainModel, Assembly>();
        var enumerations = new Dictionary<Enumeration, ITypeInfo>();
        var entities = new Dictionary<Entity, ITypeInfo>();
        var entityProperties = new Dictionary<EntityProperty, IMemberInfo>();

        var persistentTypes = typeInfos
            .Where(t => t.IsVisible && !t.IsInterface)
            .Where(t => typeof(PersistentBase).IsAssignableFrom(t.Type))
            .GroupBy(t => t.AssemblyInfo.Assembly)
            .ToDictionary(g => g.Key, g => g.ToList());

        foreach (var assembly in persistentTypes.Keys)
        {
            var domainModel = CreateDomainModel(assembly, session);
            domainModels.Add(domainModel, assembly);

            foreach (var typeInfo in persistentTypes[assembly])
            {
                var entity = CreateEntity(typeInfo, domainModel);
                entities.Add(entity, typeInfo);

                var order = 0;
                foreach (var memberInfo in typeInfo.Members.Where(m => m.IsPublic && m.IsProperty && m.Owner == typeInfo))
                {
                    var entityProperty = CreateEntityProperty(memberInfo, order++, entity);
                    entityProperties.Add(entityProperty, memberInfo);
                }
            }

            var assemblies = assembly.GetReferencedAssemblies();
        }

        foreach (var domainModel in domainModels.Keys)
        {
            var assembly = domainModels[domainModel];
            var referencedAssemblies = assembly.GetReferencedAssemblies().Select(a => a.FullName).ToList();
            var referencedDomainModels = domainModels.Keys.Where(m => referencedAssemblies.Contains(domainModels[m].FullName)).Distinct().ToList();
            domainModel.ReferenceModels.AddRange(referencedDomainModels);
        }

        foreach (var entity in entities.Keys)
        {
            var typeInfo = entities[entity];

            var baseType = typeInfo.Type.BaseType;
            if (baseType != null)
            {
                entity.BaseClass = entities.Keys.FirstOrDefault(e => entities[e].Type == baseType);
            }

            if (typeInfo.DefaultMember != null)
            {
                entity.DefaultProperty = entityProperties.Keys.FirstOrDefault(e => entityProperties[e] == typeInfo.DefaultMember);
            }

            foreach (var entityProperty in entity.Properties)
            {
                var memberInfo = entityProperties[entityProperty];
                CreateEntityPropertyType(memberInfo, entityProperty, entities, domainModels);
                CreateRequiredRule(memberInfo, entity);
                CreateUniqueValueRule(memberInfo, entity);
                CreateRangeRule(memberInfo, entity);
                CreateFromBoolPropertyRule(memberInfo, entity);
                CreateAssociation(memberInfo, entity, entities);
                CreateAppearances(memberInfo, entity, entityProperty);
            }

            CreateAppearances(typeInfo, entity);
        }

        session.CommitChanges();

        foreach (var disposableObject in objectsToDisposeOnDisconnect)
        {
            disposableObject.Dispose();
        }
    }
}
